CHAPTER 12

Delegates and Events

Delegates provide a built-in, language-supported mechanism for defining and executing callbacks. Their flexibility allows you to define the exact signature of the callback, and that information becomes part of the delegate type itself. Anonymous functions are forms of dele-gates that allow you to shortcut some of the delegate syntax that, in many cases, may be overkill. Building on top of delegates is the support for events in Visual Basic (VB) and the .NET platform. Events provide a uniform pattern for hooking up callback implementations, and possibly multiple instances thereof, to the code that triggers the callback.

Overview of Delegates

The common language runtime (CLR) provides a runtime that supports a flexible callback mechanism, and delegates are the preferred method of implementing these callbacks. When you declare a delegate in your code, the VB compiler generates a class derived from MulticastDelegate, and the CLR implements all of the interesting methods of the delegate dynamically at run time.

The delegate contains a couple of useful fields. The first one holds a reference to an object, and the second holds a method pointer. When you invoke the delegate, the instance method is called on the contained reference. However, if the object reference is Nothing, the runtime understands this to mean that the method is a shared method. One delegate type can handle callbacks to either an instance or shared method. Moreover, invoking a delegate is exactly the same syntactically as calling a regular function. Therefore, delegates are perfect for implementing callbacks.

As you can see, delegates provide an excellent mechanism to decouple the method being called on an instance from the actual caller. In fact, the caller of the delegate has no idea, or necessity to know, if it is calling an instance method or a shared method or on what exact instance it is calling. To the caller, it is calling arbitrary code. The caller can obtain the delegate instance through any appropriate means, and it can be decoupled completely from the entity it actually calls.

Think for a moment about UI elements in a dialog, such as a Commit button, and how many external parties may be interested in knowing when that button is pressed. If the class that represents the button must call directly to the interested parties, it needs to have intimate knowledge of the layout of those parties, or objects, and it must know which method to call on each one of them. Clearly, this requirement adds way too much coupling between the button class and the interested parties, and with coupling comes complexity. Delegates come to the rescue and break this link. Now, interested parties need to only register a delegate with the button that is preconfigured to call whatever method they want. This decoupling mechanism describes events as supported by the CLR. The “Events” section discusses CLR events in more detail. Let’s go ahead and see how to create and use delegates.

Delegate Creation and Use

Delegate declarations look almost exactly like method declarations, except they have one added keyword: the Delegate keyword. The following is a valid delegate declaration:

Public Delegate Function ProcessResults(ByVal x As Double, ByVal y As Double) As Double

When the compiler encounters this line, it defines a type derived from MulticastDelegate, which also implements a method named Invoke that has the exact same signature of the method described in the delegate declaration. For all practical purposes, that class looks like the following:

NotInheritable Class ProcessResults
    Inherits System.MulticastDelegate

    Public Function Invoke(ByVal x As Double, ByVal y As Double) As Double
    End Function
    'Other stuff omitted for clarity.
End Class

Even though the compiler creates a type similar to that listed, the compiler also abstracts the use of delegates behind syntactical shortcuts. In fact, the compiler won’t allow you to call the Invoke method on a delegate directly. Instead, you use a syntax that looks similar to a function call, which we’ll show shortly.

When you instantiate an instance of a delegate, you must wire it up to a method to call when it is invoked. The method that you wire it up to could be either a shared or instance method that has a signature compatible with that of the delegate. Thus, the parameter types and the return type must either match the delegate declaration, or they must be implicitly convertible to the types in the delegate declaration.